/**
 * Copyright 2010 The Regents of the University of California
 *
 *  All Rights Reserved
 */
/**
 * File: fault-manager.c
 *
 * implements fault tolerance functionality for the controller 
 *
 */

// include files

#include <pthread.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <time.h>

#include "random.h"
#include "vconn.h"
#include "fault-manager.h"
#include "flow-insertion.h"
#include "learning-switch.h"
#include "arp-handling.h"
#include "dcswitch.h"
#include "ofpbuf.h"
#include "data-structures.h"
#include "new-switch-handler.h"
#include "uthash.h"
#include "vlog.h"
#include "timeval.h"

#define THIS_MODULE VLM_fault_manager

int dist_list[MAX_NUMBER_OF_SWITCHES];
int prev_vertex_list[MAX_NUMBER_OF_SWITCHES];
int my_next_hop[K];
int determineDstSwitchIndex(int fatTreeIndex);
int matchDstSwitch(int srcSwitchIndex, int nextHop);


bool isNewSwitch(struct lswitch *sw)
{
	int index;
	index = new_lookup(sw->datapath_id);
	if(index == -1)
	{
		//Switch Discovered for the first time
		return true;
	}
	else
	{
		//Possible fault
		return false;
	}
}

/* Defines a fault in the systems. Can be modified for different scenaio
	currently we are using the fact that the hardware address of the port is 0:0:0:0:0:0 when there is fault

*/
bool isFault(void *opp_)
{
	const struct ofp_phy_port *opp = opp_;
	if(opp->hw_addr[0] == 0x00 &&
	   opp->hw_addr[1] == 0x00 &&
	   opp->hw_addr[2] == 0x00 &&
	   opp->hw_addr[3] == 0x00 &&
	   opp->hw_addr[4] == 0x00 &&
	   opp->hw_addr[5] == 0x00)
		return true;
	else
		return false;

}


void updateDCSwitch(struct lswitch *sw, void *opp_)
{
	int index,i;
	DCSwitch_node* node;
	const struct ofp_phy_port *opp = opp_;
	uint16_t port_no = ntohs(opp->port_no);
   	uint32_t state = ntohl(opp->state);
    unsigned int *port_state = &sw->port_states[port_no];
    VLOG_DBG("Inside process_phy_port switch: %d port_no = %d  hw_addr: %x:%x:%x:%x:%x:%x \n", new_lookup(sw->datapath_id),port_no, opp->hw_addr[0], opp->hw_addr[1],opp->hw_addr[2],opp->hw_addr[3], opp->hw_addr[4],opp->hw_addr[5]);
	VLOG_DBG("Switch state is %"PRIu32"\n", state);
	VLOG_DBG("port state is %d \n", *port_state);

	index = new_lookup(sw->datapath_id);
	
	node = DCSwitches[index];

 	for(i = 0; i < node->n_ports; i++)
	{	
		if(node->ports[i][0] == port_no)
		{
			//node->ports[i][0] = 0;
			node->n_ports--;
			node->neighbours[i] = NULL;
		}
	} 
	VLOG_DBG("FLOW STAT REQUEST SENT!");

	sleep_signal_safe(0, random_range(1000));
	send_flow_stats_request(node->rconn, sw, port_no);

	//HANDLING FOR CASES WHEN THE PORT IS NOT FOUND IN THE LIST
}


void insertPortDetails(struct lswitch *sw, void *opp_)
{
	int index,i;
	const struct ofp_phy_port *opp = opp_;
	uint16_t port_no = ntohs(opp->port_no);
    uint32_t state = ntohl(opp->state);
    unsigned int *port_state = &sw->port_states[port_no];
	DCSwitch_node* node;
    VLOG_DBG("Inside process_phy_port switch: %d port_no = %d  hw_addr: %x:%x:%x:%x:%x:%x \n", new_lookup(sw->datapath_id),port_no, opp->hw_addr[0], opp->hw_addr[1],opp->hw_addr[2],opp->hw_addr[3], opp->hw_addr[4],opp->hw_addr[5]);
	VLOG_DBG("Switch state is %"PRIu32"\n", state);
	VLOG_DBG("port state is %d \n", *port_state);
	
	index = new_lookup(sw->datapath_id);
	
	node = DCSwitches[index];

 	for(i = 0; i < K; i++)
	{	
		if(node->ports[i][0] == port_no)
		{
			node->n_ports++;
			break;
		}
	} 
	for(i=0;i<1; i++)
	{
		sleep_signal_safe(0, random_range(1000));
		send_probe_packet_out(node->sw, node->rconn, OFPP_NONE, port_no, node);
	}
	//HANDLING FOR CASES WHEN THE PORT IS NOT FOUND IN THE LIST
}
/*
void printPortMap(int index)
{
	int i;
	DCSwitch_node* node;
	node = DCSwitches[index];
	
	for(i=0; i<node->n_ports; i++)
	{
		VLOG_WARN(" Port : %d neighbour address: %d\n", node->ports[i][0], (node->neighbours[i])->index); 
	}
}
*/

/* Compute shortest path for switch index 'index') */
void compute_shortest_path(int index)
{
    int i;
    int visited_list[MAX_NUMBER_OF_SWITCHES];

    int cur_vertex = index;
    int visited_cntr = 0;

    int min_not_visited = -1;
    int min_dist=INF;

        VLOG_DBG("[%s] - started\n", __PRETTY_FUNCTION__);
    //Initialize dist_list
    for(i=0; i<MAX_NUMBER_OF_SWITCHES; i++)
    {
        dist_list[i]= INF;
    }
    dist_list[index]=0;

    //Initialize prev_vertex_list
    for(i=0; i<MAX_NUMBER_OF_SWITCHES; i++)
    {
        prev_vertex_list[i]= -1;
    }

    //Initialize visited_list
    for(i=0; i<MAX_NUMBER_OF_SWITCHES; i++)
    {
        visited_list[i]= 0;
    }

    do
    {
        min_not_visited = -1;
        min_dist=INF;
        visited_list[cur_vertex]=1;
        visited_cntr++;
        for(i=0; i<MAX_NUMBER_OF_SWITCHES; i++)
        {
                        //Need to code up how to access the fault matrix to be accessed
            if(isConnected(cur_vertex,i))
            {
                if(dist_list[i]>(dist_list[cur_vertex]+1))
                {
                    dist_list[i]=dist_list[cur_vertex]+1;
                    prev_vertex_list[i] = cur_vertex;
                }
            }
            if(!visited_list[i] && min_dist>= dist_list[i])
            {
                if(min_dist !=INF && min_dist==dist_list[i])
                {
                    min_dist = dist_list[i];
                    min_not_visited = i;
                }
                else
                {
                    min_dist = dist_list[i];
                    min_not_visited = i;
                }
            }
        }
        cur_vertex = min_not_visited;
    }
    while((visited_cntr != MAX_NUMBER_OF_SWITCHES));
/*
	for(i=0; i<MAX_SWITCHES; i++)
		printf("%d ", dist_list[i]);
	printf("\n");
*/
    VLOG_DBG("[%s] - completed\n", __PRETTY_FUNCTION__);
}

bool isConnected(int swIndex1, int swIndex2)
{
	int i;
        DCSwitch_node* node = DCSwitches[swIndex1];
	if(node == NULL)
		return false;
        for(i=0; i<K; i++)
        {
			DCSwitch_node peer;
            if( node->neighbours[i] != NULL)
			{
                memcpy(&peer, node->neighbours[i], sizeof(DCSwitch_node));
				if(peer.index == swIndex2)
				return true;
			}
		}
	return false;
}

int get_next_hop(int dst_index)
{
    int next_hop_index = dst_index;

    if(dst_index>(MAX_NUMBER_OF_SWITCHES-1))
    {
        VLOG_DBG("\n[%s] Not a valid destination was given as input (%d)...\n", __PRETTY_FUNCTION__, dst_index);
        return -1;
    }
    while(dist_list[next_hop_index]>1)
    {
        if(dist_list[next_hop_index]==INF)
        { //No path exists to dst_index
            next_hop_index = -1;
            break;
        }
        if (prev_vertex_list[next_hop_index] != -1)
        {
            next_hop_index = prev_vertex_list[next_hop_index];
        }
        else
        {
            //something wrong
            next_hop_index = -1;
            break;
        }
    }

    VLOG_DBG("[%s] Next hop index chosen = %d \n", __PRETTY_FUNCTION__, next_hop_index);
    return next_hop_index;
}

//uint16_t  determine_output_port(uint8_t *dl_src, uint8_t *dl_dst)
//uint16_t  determine_output_port(int index, uint8_t *dl_dst)
uint16_t  determine_output_port(int index, int pod, int position, int port)
{
    //This should take care of fault tolerance
    int output_port = OFPP_NONE;
    int next_hop;
	int dst_pod;
	int dst_pos;
	int srcSwitchIndex, dstSwitchIndex;
	int fatTreeIndex;

	dst_pod = pod; 
	dst_pos = position;
	
	srcSwitchIndex = index;
	fatTreeIndex = dst_pod * K + dst_pos;
	dstSwitchIndex = determineDstSwitchIndex(fatTreeIndex);
    VLOG_DBG("Determine output port : me = %d fattree index: %d\n", index, fatTreeIndex);

	if(index == dstSwitchIndex)
	{
		DCSwitch_node* node;
		node = DCSwitches[index];
		output_port = node->ports[port][0];
		return output_port;
	}
	
	if(dstSwitchIndex == -1)
	{
		VLOG_WARN("*****************************ERROR!");
	}

    next_hop = get_next_hop(dstSwitchIndex);
	if(next_hop == -1)
		VLOG_WARN("*************************NEXT Hop = -1");
	VLOG_DBG("Next Hop: %d", next_hop);
	output_port = matchDstSwitch(srcSwitchIndex, next_hop);
	if(output_port == -1)
	{
		VLOG_WARN("**************************Output port could not be determined for next_pod: %d", dstSwitchIndex);
	}	


    VLOG_DBG("In Determining output port: selected port =  %d\n", output_port);
    return output_port;
}

int matchDstSwitch(int srcSwitchIndex, int nextHop)
{
	int i;
	DCSwitch_node* node;
	DCSwitch_node* peer;
	node = DCSwitches[srcSwitchIndex];

	if(srcSwitchIndex == nextHop)
		VLOG_WARN("Destination in the same switch");

	for(i=0; i<K; i++)
	{
		peer = node->neighbours[i];
		if(peer != NULL && peer->index == nextHop)
			return node->ports[i][0];
	}	
	return -1;
}

int determineDstSwitchIndex(int fatTreeIndex)
{
	int i;
	DCSwitch_node* node;
	
	for(i=0; i<MAX_SWITCHES; i++)
	{
		node = DCSwitches[i];
		if(node==NULL) continue;
		if(node->fattree_index == fatTreeIndex)
		{
			return node->index;	
		}
	}
	return -1;
}

void processSwitchResponse(struct lswitch *sw, struct rconn *rconn, const struct ofp_flow_stats *fs)
{
	
	//Response from the edge switch
	//Determine the DL DST
	int i;	
	struct ofp_match *om ;
	uint16_t out_ports[K];
	int num_out_ports = 0;
	int out_index = OFPP_FLOOD;
	struct ofp_action_output *oah = (struct ofp_action_output*)&(fs->actions);
	int length = ntohs(fs->length);
	int offset = offsetof(struct ofp_flow_stats, actions);
    int actions_length = length - offset;
	location loc;
	arp_cache_record *acr;
	om = (struct ofp_match*)&(fs->match);

	for (i = 0; i < K; i++)
	{
       	out_ports[i] = (uint16_t)OFPP_NONE;
	}
    if (actions_length >= sizeof (struct ofp_action_header))
    {
    	int num_actions = actions_length / sizeof(struct ofp_action_output);
	    if (num_actions < K) //Cant output to all ports - thats a flood
        {
			for (i = 0; i < num_actions; i++)
			{	
				if (oah[i].type == htons(OFPAT_OUTPUT))
				{
					out_ports[num_out_ports] = ntohs(oah[i].port);
					VLOG_WARN("Action: Output: %d", out_ports[num_out_ports]);
                                        num_out_ports++;
					// Insert if the port is upward facing only!
					if(!isDownwordFacing(new_lookup(sw->datapath_id), out_ports[num_out_ports-1]))
					{
						VLOG_WARN("Computing shorted path for %d", new_lookup(sw->datapath_id));
						compute_shortest_path(new_lookup(sw->datapath_id));
						if(getFlowEntryMatching() == PMAC)
							out_index = determine_output_port(new_lookup(sw->datapath_id), (om->dl_dst)[3], (om->dl_dst)[4], (om->dl_dst)[5]);
						else if(getFlowEntryMatching() == PIP)
						{
							acr = arp_cache_entry(&arp_cache_table, ntohl(om->nw_dst));
							if(acr != NULL)
							{
								//AIP TO PIP translation found in the arp cache.
								//The path is determined on the basis of PIP
								VLOG_WARN("$$$$$$$$$$$$$$Dst IP: %.8x", ntohl(acr->pip));
								loc = extractLocationFromIP((acr->pip));
								VLOG_WARN("IP TRANSLATED!!!pod = %d, position = %d, port = %d", loc.pod, loc.position, loc.port);
								assert((loc.pod < K && loc.position < K && loc.port < K));	
								out_index = determine_output_port(new_lookup(sw->datapath_id), loc.pod, loc.position, loc.port);
								modifyFlowEntryPIPEdge(rconn, sw, om->nw_dst, acr->pip, out_index); 
							}
							else
							{
								//No translation information found
								//we do the path determination on the basis of the available info

								loc = extractLocationFromIP(ntohl(om->nw_dst));
								VLOG_WARN("NO IP TRANSLATION1 pod = %d, position = %d, port = %d", loc.pod, loc.position, loc.port);
								assert((loc.pod < K && loc.position < K && loc.port < K));	
								out_index = determine_output_port(new_lookup(sw->datapath_id), loc.pod, loc.position, loc.port);
								modifyFlowEntryPIP(rconn, sw, om->nw_dst, out_index); 
							}
						}
	
						//  TO DO : NEW PORT == OLD PORT! .. DONT DO ANYTHING!
						// CHECK IF THE PORT IS ALLOWED!
						VLOG_WARN("[Switch %d]  New output port: %d ORIG_PORT: %d", new_lookup(sw->datapath_id), out_index, out_ports[num_out_ports-1]);
						if(out_index != -1 && getFlowEntryMatching() == PMAC)
							modifyFlowEntry(rconn, sw, om->dl_dst, out_index); 
					}
				}
			}
		}
	}
}
